﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Infrastructure.Data;
using Keepo.Models;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Drawing;
using System.IO;
using AlbumDesigner.Models.Enums;
using System.Drawing.Imaging;
using Keepo.Helpers;

namespace AlbumDesigner.Helpers
{
    public class PdfHelper
    {
        static int Quality;

        public static KeyValuePair<bool, string> GeneratePDF(int id, int quality)
        {
            var innerDirPath = "";
            var coverDirPath = "";
            var innerFilePath = "";
            var coverFilePath = "";
            var innerFileLink = "";
            var coverFileLink = "";
            int versionIdx = 1;
            Quality = quality;

            using (var repository = new GenericRepository())
            {
                //get the OrderNum of this album
                int orderNum = 0;
                var album = repository.GetByKey<Album>(id);
                if (album != null)
                {
                    orderNum = album.OrderNum;
                }

                //get the last version in this album
                var lastVersion = repository.Find<AlbumPrintVersion>(m => m.AlbumId == id).OrderByDescending(m => m.VersionIdx).FirstOrDefault();
                if (lastVersion != null)
                {
                    versionIdx = lastVersion.VersionIdx + 1;
                }

                var innerFileName = orderNum + "_" + versionIdx + "_" + album.AlbumSize.Title + "_book.pdf";
                var coverFileName = orderNum + "_" + versionIdx + "_" + album.AlbumSize.Title + "_cover.pdf";

                //set the file path for inner
                innerDirPath = HttpContext.Current.Server.MapPath("~/resources/albums/" + id + "/pdf/inner/");
                innerFilePath = innerDirPath + innerFileName;
                innerFileLink = "resources/albums/" + id + "/pdf/inner/" + innerFileName;

                //set the file path for cover
                coverDirPath = HttpContext.Current.Server.MapPath("~/resources/albums/" + id + "/pdf/cover/");
                coverFilePath = coverDirPath + coverFileName;
                coverFileLink = "resources/albums/" + id + "/pdf/cover/" + coverFileName;


                if (!Directory.Exists(innerDirPath))
                {
                    Directory.CreateDirectory(innerDirPath);
                }
                if (!Directory.Exists(coverDirPath))
                {
                    Directory.CreateDirectory(coverDirPath);
                }

                // Generate Pages pdf
                var generatePdf = GeneratePDFDocument(id, false, innerFilePath);

                // Generate Cover's pdf
                var generateCoverPdf = GeneratePDFDocument(id, true, coverFilePath);

                // delete the render task from DB
                var renderTask = repository.Find<RenderTask>(m => m.AlbumId == id).FirstOrDefault();
                if (renderTask != null)
                {
                    repository.Delete<RenderTask>(renderTask);
                    repository.UnitOfWork.SaveChanges();
                }

                if (!generatePdf.Key)
                {
                    return generatePdf;
                }

                if (!generateCoverPdf.Key)
                {
                    return generateCoverPdf;
                }

                //set "HasRender" field of this Album to true
                album.HasRender = true;
                repository.UnitOfWork.SaveChanges();

                //add new AlbumPrintVersion record to DB
                var printVersion = new AlbumPrintVersion();
                printVersion.AlbumId = id;
                printVersion.AlbumPrintVersionStatusId = 1; //default status
                printVersion.VersionDate = DateTime.Now;
                printVersion.VersionIdx = versionIdx;
                printVersion.ShowToAlbumEditor = false;
                printVersion.InnerUrl = innerFileLink;
                printVersion.CoverUrl = coverFileLink;

                repository.Add<AlbumPrintVersion>(printVersion);
                repository.UnitOfWork.SaveChanges();

                return new KeyValuePair<bool, string>(false, "תהליך הרנדור הסתיים בהצלחה");
            }
        }


        public static int[] GetInternalPageIdsSorted(int albumId)
        {
            using (var db = new AlbumContext())
            {
                // Select internal pages by album id
                var pages = db.AlbumPages.Where(m => m.AlbumSection.AlbumId == albumId && m.AlbumSection.AlbumSectionTypeId != (int)AlbumSectionTypeEnum.Cover);
                var ids = pages.OrderBy(m => m.AlbumSection.SectionIdx).ThenBy(m => m.SectionPageIdx).Select(p => p.AlbumPageId).ToArray();

                return ids;
            }

        }

        public static Bitmap MergeSinglePageBitmap(AlbumPage leftPage, AlbumPage rightPage)
        {
            var leftImagePath = AlbumHelper.PageFullImage(leftPage.AlbumSection.AlbumId, leftPage.AlbumPageId);
            leftImagePath = HttpContext.Current.Server.MapPath("~/" + leftImagePath);

            var rightImagePath = AlbumHelper.PageFullImage(rightPage.AlbumSection.AlbumId, rightPage.AlbumPageId);
            rightImagePath = HttpContext.Current.Server.MapPath("~/" + rightImagePath);

            var leftBitmap = new Bitmap(leftImagePath);
            var rightBitmap = new Bitmap(rightImagePath);

            // var destRect = new System.Drawing.Rectangle(0, 0, leftBitmap.Width / 2, leftBitmap.Height);
            var srcRect = new System.Drawing.Rectangle(leftBitmap.Width / 2, 0, leftBitmap.Width / 2, leftBitmap.Height);

            Graphics g = Graphics.FromImage(leftBitmap);
            g.DrawImage(rightBitmap, srcRect, srcRect, GraphicsUnit.Pixel);

            rightBitmap.Dispose();

            return leftBitmap;
        }

        // This function returns the full image according to page id,
        // In case it's a single page, merges it with the next single to compose a double spread
        public static Bitmap GetFullImageForAlbumPage(int pageId)
        {
            using (var db = new AlbumContext())
            {
                var albumPage = db.AlbumPages.Find(pageId);


                if (albumPage.IsSingle)
                {
                    var pageIds = GetInternalPageIdsSorted(albumPage.AlbumSection.AlbumId);
                    var pageIdx = Array.IndexOf(pageIds, pageId);

                    AlbumPage leftPage = null;
                    AlbumPage rightPage = null;

                    // case it's left side
                    if (albumPage.IsLeft)
                    {
                        leftPage = albumPage;
                        var rightIdx = pageIdx - 1;

                        if (rightIdx > 0)
                        {
                            rightPage = db.AlbumPages.Find(pageIds[rightIdx]);
                            return MergeSinglePageBitmap(leftPage, rightPage);
                        }
                    }
                    // case it's right side
                    else
                    {
                        rightPage = albumPage;
                        var leftIdx = pageIdx + 1;

                        if (leftIdx < pageIds.Length)
                        {
                            leftPage = db.AlbumPages.Find(pageIds[leftIdx]);
                            return MergeSinglePageBitmap(leftPage, rightPage);
                        }
                    }
                }

                // If it's not single or no page to merge with - just return the image
                var imagePath = AlbumHelper.PageFullImage(albumPage.AlbumSection.AlbumId, albumPage.AlbumPageId);
                imagePath = HttpContext.Current.Server.MapPath("~/" + imagePath);
                return new Bitmap(imagePath);

            }
        }

        // The internal image size in pixels
        public static Size CroppedImageSizePix(Album album, Boolean isCover)
        {
            Size result = new Size();

            var albumSize = album.AlbumSize;

            if (isCover)
            {
                float spineWidthMm = AlbumHelper.CalculateSpineWidthMm(album, album.Paper);
                result.Width = AlbumPagesHelper.MMToPixels(albumSize.PageWidthMM + (2 * albumSize.CoverBleedMM) + (2 * albumSize.BigSizeMM) + spineWidthMm);
                result.Height = AlbumPagesHelper.MMToPixels(albumSize.PageHeightMM + (2 * albumSize.CoverBleedMM));
            }
            else
            {
                result.Width = AlbumPagesHelper.MMToPixels((albumSize.PageWidthMM / 2) + (2 * albumSize.PageBleedMM));
                result.Height = AlbumPagesHelper.MMToPixels(albumSize.PageHeightMM + (2 * albumSize.PageBleedMM));
            }

            return result;
        }

        private static Size Pix300To72(Size input)
        {
            var factor = 72f / 300f;
            var result = new Size();

            result.Height = (int)(input.Height * factor);
            result.Width = (int)(input.Width * factor);

            return result;
        }

        private static int MMToPix(float value, int dpi = 300)
        {
            return (int)(value / 25.4 * dpi);
        }

        private static Point MMToPix(PointF input, int dpi = 300)
        {
            return new Point(MMToPix(input.X, dpi), MMToPix(input.Y, dpi));
        }

        private static Point BleedOffset72Pix(AlbumSize albumSize)
        {
            return MMToPix(new PointF(albumSize.PageBleedMM, albumSize.PageBleedMM), 72);
        }

        private static List<AlbumPage> AlbumPagesById(int id, bool isCoverPdf)
        {
            List<AlbumPage> albumPages;

            // Retrieve Album
            Album album;
            using (var db = new AlbumContext())
            {
                album = db.Albums.Find(id);

                //get the pages of this Album (regular pages or cover pages)
                if (isCoverPdf)
                {
                    albumPages = db.AlbumPages.Where(m => m.AlbumSection.AlbumId == id && m.AlbumSection.AlbumSectionTypeId == (int)AlbumSectionTypeEnum.Cover).OrderBy(m => m.AlbumSection.SectionIdx).ThenBy(m => m.SectionPageIdx).ToList();
                }
                else
                {
                    albumPages = db.AlbumPages.Where(m => m.AlbumSection.AlbumId == id && m.AlbumSection.AlbumSectionTypeId != (int)AlbumSectionTypeEnum.Cover).OrderBy(m => m.AlbumSection.SectionIdx).ThenBy(m => m.SectionPageIdx).ToList();
                }
            }

            return albumPages;
        }

        private static Bitmap cropCenter(Bitmap bitmap, Size imageSizePix)
        {
            var offsetX = (bitmap.Width - imageSizePix.Width) / 2;
            var offsetY = (bitmap.Height - imageSizePix.Height) / 2;

            var centerRect = new System.Drawing.Rectangle(offsetX, offsetY, imageSizePix.Width, imageSizePix.Height);
            return bitmap.Clone(centerRect, bitmap.PixelFormat);
        }

        private static KeyValuePair<bool, string> GeneratePDFDocument(int id, bool isCoverPdf, string filePath)
        {
            // convert from 72DPI to 300DPI
            // 72/300 * 100 = 24%
            var resizeTo300Dpi = 72f / 300f;
            var scalePercent = resizeTo300Dpi * 100;

            // Retrieve Album
            using (var db = new AlbumContext())
            {
                var album = db.Albums.Find(id);

                // Get AlbumSize
                var albumSize = album.AlbumSize;

                // Get AlbumPages
                var albumPages = AlbumPagesById(id, isCoverPdf);

                // calculate the actual spine width according to pages number * paper thickness
                float spineWidthMm = AlbumHelper.CalculateSpineWidthMm(album, album.Paper);

                // Calculate Image Size
                Size imageSizePix = CroppedImageSizePix(album, isCoverPdf);
                Size imageSize72Pix = Pix300To72(imageSizePix);

                // Calculate Pdf Size
                Size pdfSizePix = PdfSizeFromImageSizePix(imageSizePix);
                Size pdfSize72Pix = Pix300To72(pdfSizePix);
                var pageSize72Pix = new iTextSharp.text.Rectangle(pdfSize72Pix.Width, pdfSize72Pix.Height);

                // calculate the offset of printable image (where should the image start on the PDF document) - vertical and horizontal centering
                Point imageOffset72Pix = new Point(Size.Subtract(pdfSize72Pix, imageSize72Pix));
                imageOffset72Pix.X = (int)(imageOffset72Pix.X * 0.5f);
                imageOffset72Pix.Y = (int)(imageOffset72Pix.Y * 0.5f);

                Point bleedOffset72Pix = BleedOffset72Pix(album.AlbumSize);
                bleedOffset72Pix = Point.Add(bleedOffset72Pix, new Size(imageOffset72Pix));

                //create PDF document
                Document myDocument = new Document(pageSize72Pix, 0, 0, 0, 0);
                var writer = PdfWriter.GetInstance(myDocument, new FileStream(filePath, FileMode.Create));
                myDocument.Open();
                PdfContentByte pdfContent = writer.DirectContent;

                //loop through the pages
                foreach (var albumPage in albumPages)
                {
                    myDocument.NewPage();

                    // Get Full image for page 
                    Bitmap bitmap = GetFullImageForAlbumPage(albumPage.AlbumPageId);

                    //if this is Cover PDF
                    if (isCoverPdf)
                    {
                        // crop central part of image
                        var centerBitmap = cropCenter(bitmap, imageSizePix);
                        DrawImageOnPdf(myDocument, scalePercent, imageOffset72Pix.X, imageOffset72Pix.Y, centerBitmap);
                        DrawLogoOnCover(myDocument, imageSize72Pix, bleedOffset72Pix);
                    }
                    //if regular pages PDF
                    else
                    {
                        Bitmap leftSideImage;
                        Bitmap rightSideImage;

                        System.Drawing.Imaging.PixelFormat format = bitmap.PixelFormat;

                        // Crop the full image for left-side page
                        var leftRect = new System.Drawing.Rectangle(0, 0, imageSizePix.Width, imageSizePix.Height);
                        leftSideImage = bitmap.Clone(leftRect, format);

                        // Crop the full image for right-side page
                        var rightRect = new System.Drawing.Rectangle(bitmap.Width - imageSizePix.Width, 0, imageSizePix.Width, imageSizePix.Height);
                        rightSideImage = bitmap.Clone(rightRect, format);

                        if (albumPage.IsSingle)
                        {
                            //for Right-side page
                            if (!albumPage.IsLeft)
                            {
                                DrawImageOnPdf(myDocument, scalePercent, imageOffset72Pix.X, imageOffset72Pix.Y, rightSideImage);
                            }
                            //for Left-side page
                            else
                            {
                                DrawImageOnPdf(myDocument, scalePercent, imageOffset72Pix.X, imageOffset72Pix.Y, leftSideImage);
                            }
                        }
                        else
                        {
                            /*Right-side page (from this double page)
                             ---------------------------------------*/
                            DrawImageOnPdf(myDocument, scalePercent, imageOffset72Pix.X, imageOffset72Pix.Y, rightSideImage);

                            DrawBleedMarksOnPDFPage(myDocument, pdfContent, bleedOffset72Pix.X, bleedOffset72Pix.Y, imageOffset72Pix.X, imageOffset72Pix.Y);

                            /*Left-side page (from this double page)
                             --------------------------------------*/
                            myDocument.NewPage();

                            DrawImageOnPdf(myDocument, scalePercent, imageOffset72Pix.X, imageOffset72Pix.Y, leftSideImage);
                        }

                        leftSideImage.Dispose();
                        rightSideImage.Dispose();
                    }

                    bitmap.Dispose();

                    // Draw Cutting marks
                    DrawBleedMarksOnPDFPage(myDocument, pdfContent, bleedOffset72Pix.X, bleedOffset72Pix.Y, imageOffset72Pix.X, imageOffset72Pix.Y);

                    //update DB - increase the RenderedPages count of this RenderTask
                    var renderTask = db.RenderTasks.Where(m => m.AlbumId == id).FirstOrDefault();
                    if (renderTask != null)
                    {
                        renderTask.RenderedPages++;
                        db.SaveChanges();
                    }
                    
                }

                myDocument.Dispose();
                myDocument.Close();
            }
            return new KeyValuePair<bool, string>(true, "");
        }

        private static void DrawBleedMarksOnPDFPage(Document myDocument, PdfContentByte pdfContent, float bleedOffsetX, float bleedOffsetY, float imageOffsetX, float imageOffsetY)
        {
            pdfContent.SetLineWidth(0.5f);

            //bottom left (horizontal line)
            pdfContent.MoveTo(0, bleedOffsetY);
            pdfContent.LineTo(imageOffsetX, bleedOffsetY);
            pdfContent.Stroke();

            //top left (horizontal line)
            pdfContent.MoveTo(0, myDocument.PageSize.Height - bleedOffsetY);
            pdfContent.LineTo(imageOffsetX, myDocument.PageSize.Height - bleedOffsetY);
            pdfContent.Stroke();

            //bottom right (horizontal line)
            pdfContent.MoveTo(myDocument.PageSize.Width - imageOffsetX, bleedOffsetY);
            pdfContent.LineTo(myDocument.PageSize.Width, bleedOffsetY);
            pdfContent.Stroke();

            //top right (horizontal line)
            pdfContent.MoveTo(myDocument.PageSize.Width - imageOffsetX, myDocument.PageSize.Height - bleedOffsetY);
            pdfContent.LineTo(myDocument.PageSize.Width, myDocument.PageSize.Height - bleedOffsetY);
            pdfContent.Stroke();


            //bottom left (vertical line)
            pdfContent.MoveTo(bleedOffsetX, 0);
            pdfContent.LineTo(bleedOffsetX, imageOffsetY);
            pdfContent.Stroke();

            //top left (vertical line)
            pdfContent.MoveTo(bleedOffsetX, myDocument.PageSize.Height - imageOffsetY);
            pdfContent.LineTo(bleedOffsetX, myDocument.PageSize.Height);
            pdfContent.Stroke();

            //bottom right (vertical line)
            pdfContent.MoveTo(myDocument.PageSize.Width - bleedOffsetX, 0);
            pdfContent.LineTo(myDocument.PageSize.Width - bleedOffsetX, imageOffsetY);
            pdfContent.Stroke();

            //top right (vertical line)
            pdfContent.MoveTo(myDocument.PageSize.Width - bleedOffsetX, myDocument.PageSize.Height - imageOffsetY);
            pdfContent.LineTo(myDocument.PageSize.Width - bleedOffsetX, myDocument.PageSize.Height);
            pdfContent.Stroke();
        }

        // Adds the white offset
        private static Size PdfSizeFromImageSizePix(Size imageSizePix)
        {
            float whiteOffsetMm = 5;
            var whiteOffsetPix = AlbumPagesHelper.MMToPixels(2 * whiteOffsetMm);
            return imageSizePix + new Size(whiteOffsetPix, whiteOffsetPix);
        }

        private static int AddWhiteOffset(int size)
        {
            float whiteOffsetMm = 5;
            size = size + AlbumPagesHelper.MMToPixels(2 * whiteOffsetMm);
            return size;
        }

        private static void DrawLogoOnCover(Document myDocument, Size imageSize72Pix, Point bleedOffset72Pix)
        {
            var path = HttpContext.Current.Server.MapPath("~/Content/images/elinirCoverLogo.png");
            var image = new Bitmap(path);

            var resizeTo300Dpi = 72f / 300f;
            var scalePercent = resizeTo300Dpi * 100;

            int dx = (int)(image.Width * resizeTo300Dpi);
            int offset72pix = 28;
            //DrawImageOnPdf(myDocument, scalePercent, bleedOffset72Pix.X, bleedOffset72Pix.Y, image);

            //append the image into the PDF

            iTextSharp.text.Image pdfImage = iTextSharp.text.Image.GetInstance(path);
            pdfImage.ScalePercent(scalePercent);
            pdfImage.SetAbsolutePosition(imageSize72Pix.Width - dx - offset72pix, 
                bleedOffset72Pix.Y + offset72pix);

            myDocument.Add(pdfImage);
            pdfImage = null;

            return;
        }

        private static void DrawImageOnPdf(Document myDocument, float scalePercent, float imageOffsetX, float imageOffsetY, Bitmap image)
        {
            //define the render quality of the image
            EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, (long)Quality);
            ImageCodecInfo imageCodec = ImageHelper.GetEncoderInfo("image/jpeg");
            EncoderParameters encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = qualityParam;

            using (var ms = new MemoryStream())
            {
                //write the image to the MemoryStream
                image.Save(ms, imageCodec, encoderParams);
                ms.Position = 0;

                //append the image into the PDF
                iTextSharp.text.Image pdfImage = iTextSharp.text.Image.GetInstance(ms);
                pdfImage.ScalePercent(scalePercent);
                pdfImage.SetAbsolutePosition(imageOffsetX, imageOffsetY);

                myDocument.Add(pdfImage);
                pdfImage = null;
            }
        }
    }
}